
package cn.tang.tframe.springredis;

import cn.tang.tframe.config.QtMonitorConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Created by tanghc on 17/1/13.
 */
public class QtProjectRedisRepository<V> implements ISpringRedisCache<V> {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	private String prefix = "";
	private RedisTemplate<String, V> redisTemplate;

	@Override
	public RedisTemplate<String, V> getRedisTemplate() {

		return redisTemplate;
	}

	public void setRedisTemplate(RedisTemplate<String, V> redisTemplate) {

		this.redisTemplate = redisTemplate;
	}

	public String getPrefix() {

		return prefix;
	}

	public void setPrefix(String prefix) {

		this.prefix = prefix;
	}

	private BoundValueOperations<String, V> getBoundValueOps(String key) {

		return redisTemplate.boundValueOps(key);
	}

	private BoundZSetOperations<String, V> getBoundZSetOps(String key) {

		return redisTemplate.boundZSetOps(key);
	}

	private BoundSetOperations<String, V> getBoundSetOps(String key) {

		return redisTemplate.boundSetOps(key);
	}

	private BoundListOperations<String, V> getBoundListOps(String key) {

		return redisTemplate.boundListOps(key);
	}

	private <HK, HV> BoundHashOperations<String, HK, HV> getBoundHashOps(
		String key) {

		return redisTemplate.boundHashOps(key);
	}
	// Key

	@Override
	public void del(final String key) {

		this.delegateExecute(key, new InvokerMethodInfo(false,"del"), new DelegateExecutor<String>() {

			@Override
			public <T> T doExecute(String key) {

				redisTemplate.delete(key);
				return null;
			}
		});
	}

	@Override
	public void del(final Collection<String> keys) {

		this.delegateExecute(keys, new InvokerMethodInfo(false,"delkeys"), new DelegateExecutor<Collection<String>>() {

			@Override
			public <T> T doExecute(Collection<String> keys) {
				Set<String> realKeys = null;
				if(null != keys){
					realKeys = new HashSet<>();
					for(String key: keys){
						if(!key.startsWith(prefix)){
							key = wrapPrefix(key);
						}
						realKeys.add(key);
					}
				}
				redisTemplate.delete(realKeys);
				return null;
			}
		});
	}

	@Override
	public Boolean exists(final String key) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"exists"), new DelegateExecutor<String>() {

			@Override
			public Boolean doExecute(String key) {

				return redisTemplate.hasKey(key);
			}
		});
	}

	@Override
	public Boolean expire(
		final String key, final long timeout, final TimeUnit unit) {


		return this.delegateExecute(key,  new InvokerMethodInfo(false,"expire"), new DelegateExecutor<String>() {

			@Override
			public Boolean doExecute(String key) {

				return redisTemplate.expire(key, timeout, unit);
			}
		});
	}

	@Override
	public void expireAt(final String key, final Date date) {

		this.delegateExecute(key, new InvokerMethodInfo(false,"expireAt"), new DelegateExecutor<String>() {

			@Override
			public <T> T doExecute(String key) {

				redisTemplate.expireAt(key, date);
				return null;
			}
		});
	}

	@Override
	public Set<String> keys(final String pattern) {

		return this.delegateExecute(pattern, new InvokerMethodInfo(true,"keys"), new DelegateExecutor<String>() {

			@Override
			public Set<String> doExecute(String key) {

				return redisTemplate.keys(key);
			}

		});

	}

	@Override
	public String type(final String key) {

		return this.delegateExecute(key, new InvokerMethodInfo(true,"type"), new DelegateExecutor<String>() {

			@Override
			public String doExecute(String key) {

				return redisTemplate.type(key).code();
			}

		});
	}

	@Override
	public V get(final String key) {

		return this.delegateExecute(key, new InvokerMethodInfo(true,"get"), new DelegateExecutor<String>() {

			@Override
			public V doExecute(String key) {

				BoundValueOperations<String, V> ops = getBoundValueOps(key);
				return ops.get();
			}
		});
	}

	@Override
	public V getSet(final String key, final V value) {

		return this.delegateExecute(key, new InvokerMethodInfo(true,"getSet"), new DelegateExecutor<String>() {

			@Override
			public V doExecute(String key) {

				BoundValueOperations<String, V> ops = getBoundValueOps(key);
				return ops.getAndSet(value);
			}
		});
	}

	@Override
	public Long incr(final String key, final long delta) {

		return this.delegateExecute(key, new InvokerMethodInfo(false,"incr"), new DelegateExecutor<String>() {

			@Override
			public Long doExecute(String key) {

				BoundValueOperations<String, V> ops = getBoundValueOps(key);
				return ops.increment(delta);
			}
		});
	}

	@Override
	public void set(final String key, final V value) {

		this.delegateExecute(key, new InvokerMethodInfo(false,"set"), new DelegateExecutor<String>() {

			@Override
			public <T> T doExecute(String key) {

				BoundValueOperations<String, V> ops = getBoundValueOps(key);
				ops.set(value);
				return null;
			}
		});
	}

	@Override
	public void set(
		final String key, final V value, final long timeout,
		final TimeUnit unit) {

		this.delegateExecute(key, new InvokerMethodInfo(false,"set"), new DelegateExecutor<String>() {

			@Override
			public <T> T doExecute(String key) {

				BoundValueOperations<String, V> ops = getBoundValueOps(key);
				ops.set(value, timeout, unit);
				return null;
			}
		});
	}

	@Override
	public boolean setNX(final String key, final V value) {

		return this.delegateExecute(key, new InvokerMethodInfo(false,"setNX"), new DelegateExecutor<String>() {

			@Override
			public Boolean doExecute(String key) {

				BoundValueOperations<String, V> ops = getBoundValueOps(key);
				Boolean aBoolean = ops.setIfAbsent(value);
				return null != aBoolean && aBoolean;
			}
		});
	}

	// Hash
	@Override
	public void hDel(final String key, final Object... hKeys) {

		this.delegateExecute(key, new InvokerMethodInfo(false,"hDel"), new DelegateExecutor<String>() {

			@Override
			public <T> T doExecute(String key) {

				BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
				ops.delete(hKeys);
				return null;
			}
		});
	}

	@Override
	public Boolean hExists(final String key, final String hKeys) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"hExists"), new DelegateExecutor<String>() {

			@Override
			public Boolean doExecute(String key) {

				BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
				return ops.hasKey(hKeys);
			}
		});
	}

	@Override
	public Map<String, V> hGet(final String key) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"hGet"), new DelegateExecutor<String>() {

			@Override
			public Map<String, V> doExecute(String key) {

				BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
				return ops.entries();
			}
		});
	}

	@Override
	public V hGet(final String key, final String hKey) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"hGet"), new DelegateExecutor<String>() {

			@Override
			public V doExecute(String key) {

				BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
				return ops.get(hKey);
			}
		});
	}

	@Override
	public Set<String> hKeys(final String key) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"hKeys"), new DelegateExecutor<String>() {

			@Override
			public Set<String> doExecute(String key) {

				BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
				return ops.keys();
			}
		});
	}

	@Override
	public Long hLen(final String key) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"hLen"), new DelegateExecutor<String>() {

			@Override
			public Long doExecute(String key) {

				BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
				return ops.size();
			}
		});
	}

	@Override
	public void hSet(final String key, final String hk, final V hv) {


		this.delegateExecute(key, new InvokerMethodInfo(false,"hSet"), new DelegateExecutor<String>() {

			@Override
			public <T> T doExecute(String key) {

				BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
				ops.put(hk, hv);
				return null;
			}
		});
	}

	@Override
	public void hSet(final String key, final Map<String, V> map) {

		this.delegateExecute(key, new InvokerMethodInfo(false,"hSet"), new DelegateExecutor<String>() {

			@Override
			public <T> T doExecute(String key) {

				BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
				ops.putAll(map);
				return null;
			}
		});
	}

	@Override
	public List<V> hVals(final String key) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"hVals"), new DelegateExecutor<String>() {

			@Override
			public List<V> doExecute(String key) {

				BoundHashOperations<String, String, V> ops = getBoundHashOps(key);
				return ops.values();
			}
		});
	}

	// List
	@Override
	public V lIndex(final String key, final long index) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"lIndex"), new DelegateExecutor<String>() {

			@Override
			public V doExecute(String key) {

				BoundListOperations<String, V> ops = getBoundListOps(key);
				return ops.index(index);
			}
		});
	}

	@Override
	public void lInsert(final String key, final long index, final V value) {


		this.delegateExecute(key, new InvokerMethodInfo(false,"lInsert"), new DelegateExecutor<String>() {

			@Override
			public <T> T doExecute(String key) {

				BoundListOperations<String, V> ops = getBoundListOps(key);
				ops.set(index, value);
				return null;
			}
		});
	}

	@Override
	public Long lLen(final String key) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"lLen"), new DelegateExecutor<String>() {

			@Override
			public Long doExecute(String key) {

				BoundListOperations<String, V> ops = getBoundListOps(key);
				return ops.size();
			}
		});
	}

	@Override
	public V lPop(final String key) {

		return this.delegateExecute(key, new InvokerMethodInfo(true,"lPop"), new DelegateExecutor<String>() {

			@Override
			public V doExecute(String key) {

				BoundListOperations<String, V> ops = getBoundListOps(key);
				return ops.leftPop();
			}
		});
	}

	@Override
	public V lPop(final String key, final long timeout, final TimeUnit unit) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"lPop"), new DelegateExecutor<String>() {

			@Override
			public V doExecute(String key) {

				BoundListOperations<String, V> ops = getBoundListOps(key);
				return ops.leftPop(timeout, unit);
			}
		});
	}

	@Override
	public Long lPush(final String key, final V value) {


		return this.delegateExecute(key, new InvokerMethodInfo(false,"lPush"), new DelegateExecutor<String>() {

			@Override
			public Long doExecute(String key) {

				BoundListOperations<String, V> ops = getBoundListOps(key);
				return ops.leftPush(value);
			}
		});
	}

	@Override
	public List<V> lRange(final String key, final long start, final long end) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"lRange"), new DelegateExecutor<String>() {

			@Override
			public List<V> doExecute(String key) {

				BoundListOperations<String, V> ops = getBoundListOps(key);
				return ops.range(start, end);
			}
		});
	}

	@Override
	public Long lRem(final String key, final long index, final V value) {


		return this.delegateExecute(key, new InvokerMethodInfo(false,"lRem"), new DelegateExecutor<String>() {

			@Override
			public Long doExecute(String key) {

				BoundListOperations<String, V> ops = getBoundListOps(key);
				return ops.remove(index, value);
			}
		});
	}

	@Override
	public void lSet(final String key, final long index, final V value) {


		this.delegateExecute(key, new InvokerMethodInfo(false,"lSet"), new DelegateExecutor<String>() {

			@Override
			public <T> T doExecute(String key) {

				BoundListOperations<String, V> ops = getBoundListOps(key);
				ops.set(index, value);
				return null;
			}
		});
	}

	@Override
	public void ltrim(final String key, final long start, final long end) {

		this.delegateExecute(key, new InvokerMethodInfo(false,"ltrim"), new DelegateExecutor<String>() {

			@Override
			public <T> T doExecute(String key) {
				BoundListOperations<String, V> ops = getBoundListOps(key);
				ops.trim(start, end);
				return null;
			}
		});
	}

	@Override
	public Long rPush(final String key, final V value) {


		return this.delegateExecute(key, new InvokerMethodInfo(false,"rPush"), new DelegateExecutor<String>() {

			@Override
			public Long doExecute(String key) {
				BoundListOperations<String, V> ops = getBoundListOps(key);
				return ops.rightPush(value);
			}
		});
	}

	@Override
	public V rPop(final String key) {

		return this.delegateExecute(key, new InvokerMethodInfo(true,"rPop"), new DelegateExecutor<String>() {

			@Override
			public V doExecute(String key) {
				BoundListOperations<String, V> ops = getBoundListOps(key);
				return ops.rightPop();
			}
		});
	}

	// Set
	@Override
	public Long sAdd(final String key, final V value) {


		return this.delegateExecute(key, new InvokerMethodInfo(false,"sAdd"), new DelegateExecutor<String>() {

			@Override
			public Long doExecute(String key) {
				BoundSetOperations<String, V> ops = getBoundSetOps(key);
				return ops.add(value);
			}
		});
	}

	@Override
	public Set<V> sDiff(final String key) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"sDiff"), new DelegateExecutor<String>() {

			@Override
			public Set<V> doExecute(String key) {
				BoundSetOperations<String, V> ops = getBoundSetOps(key);
				return ops.diff(key);
			}
		});
	}

	@Override
	public Set<V> sMembers(final String key) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"sMembers"), new DelegateExecutor<String>() {

			@Override
			public Set<V> doExecute(String key) {
				BoundSetOperations<String, V> ops = getBoundSetOps(key);
				return ops.members();
			}
		});
	}

	@Override
	public Boolean sIsMember(final String key, final V value) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"sIsMember"), new DelegateExecutor<String>() {

			@Override
			public Boolean doExecute(String key) {

				BoundSetOperations<String, V> ops = getBoundSetOps(key);
				return ops.isMember(value);
			}
		});
	}

	@Override
	public V sPop(final String key) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"sPop"), new DelegateExecutor<String>() {

			@Override
			public V doExecute(String key) {

				BoundSetOperations<String, V> ops = getBoundSetOps(key);
				return ops.pop();
			}
		});
	}

	@Override
	public Long sRem(final String key, final V value) {


		return this.delegateExecute(key, new InvokerMethodInfo(false,"sRem"), new DelegateExecutor<String>() {

			@Override
			public Long doExecute(String key) {

				BoundSetOperations<String, V> ops = getBoundSetOps(key);
				return ops.remove(value);
			}
		});
	}

	@Override
	public Long sCard(String key) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"sCard"), new DelegateExecutor<String>() {

			@Override
			public Long doExecute(String key) {

				BoundSetOperations<String, V> ops = getBoundSetOps(key);
				return ops.size();
			}
		});
	}

	// SortedSet
	@Override
	public void zAdd(final String key, final V value, final double score) {

		this.delegateExecute(key, new InvokerMethodInfo(false,"zAdd"), new DelegateExecutor<String>() {

			@Override
			public <T> T doExecute(String key) {

				BoundZSetOperations<String, V> ops = getBoundZSetOps(key);
				ops.add(value, score);
				return null;
			}
		});
	}

	@Override
	public Set<V> zRange(final String key, final long start, final long end) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"zRange"), new DelegateExecutor<String>() {

			@Override
			public Set<V> doExecute(String key) {

				BoundZSetOperations<String, V> ops = getBoundZSetOps(key);
				return ops.range(start, end);
			}
		});
	}

	@Override
	public Long zRem(final String key, final Object... values) {


		return this.delegateExecute(key, new InvokerMethodInfo(false,"zRem"), new DelegateExecutor<String>() {

			@Override
			public Long doExecute(String key) {

				BoundZSetOperations<String, V> ops = getBoundZSetOps(key);
				return ops.remove(values);
			}
		});
	}

	@Override
	public Long zCard(String key) {


		return this.delegateExecute(key, new InvokerMethodInfo(true,"zCard"), new DelegateExecutor<String>() {

			@Override
			public Long doExecute(String key) {

				BoundZSetOperations<String, V> ops = getBoundZSetOps(key);
				return ops.zCard();
			}
		});
	}

	@Override
	public <T> T execute(
		final RedisScript<T> script, final List<String> keys,
		final Object... args) {

		return this.delegateExecute("l_u_a", new InvokerMethodInfo(false,"executeLua"), new DelegateExecutor<String>() {
			@Override
			public T doExecute(String key) {

				return redisTemplate.execute(script, keys, args);
			}
		});

	}

	@Override
	public <T> T execute(
		final RedisScript<T> script, final RedisSerializer<?> argsSerializer,
		final RedisSerializer<T> resultSerializer, final List<String> keys,
		final Object... args) {

		return this.delegateExecute("l_u_a", new InvokerMethodInfo(false,"executeLua"), new DelegateExecutor<String>() {
			@Override
			public T doExecute(String key) {

				return redisTemplate.execute(
						script, argsSerializer, resultSerializer, keys, args);
			}
		});

	}
	@Override
	public <T> T execute(final RedisCallback<T> action)  {

		return this.delegateExecute("RedisCallback", new InvokerMethodInfo(false,"execute"), new DelegateExecutor<String>() {
			@Override
			public T doExecute(String key) {

				return redisTemplate.execute(action);
			}
		});

	}
	@Override
	public <T> T execute(final SessionCallback<T> session)  {

		return this.delegateExecute("SessionCallback", new InvokerMethodInfo(false,"execute"), new DelegateExecutor<String>() {
			@Override
			public T doExecute(String key) {

				return redisTemplate.execute(session);
			}
		});

	}

	public <K, V> V delegateExecute(
		K key, InvokerMethodInfo methodInfo, DelegateExecutor<K> delegateExecutor) {
		logger.debug("========== monitor ====  springredis  =========={}",methodInfo);
		if (null != delegateExecutor) {
			long startTime = System.currentTimeMillis();
			V t = null;
			K realKey = null;
			try {
				realKey = delegateExecutor.wrapKey(key);
				t = delegateExecutor.doExecute(realKey);
				return t;
			}
			finally {
				if (QtMonitorConfig.redis_on) {
					long endTime = System.currentTimeMillis();
					monitorRedis(methodInfo, key, realKey, t, endTime - startTime);
				}

			}
		}
		return null;
	}

	public class InvokerMethodInfo implements Serializable{
		private static final long serialVersionUID = 4709588644668370278L;
		private boolean isRead = false;
		private String methodName ;

		public InvokerMethodInfo(boolean isRead, String methodName) {
			this.isRead = isRead;
			this.methodName = methodName;
		}

		public boolean isRead() {
			return isRead;
		}

		public void setIsRead(boolean isRead) {
			this.isRead = isRead;
		}

		public String getMethodName() {
			return methodName;
		}

		public void setMethodName(String methodName) {
			this.methodName = methodName;
		}
	}

	public abstract class DelegateExecutor<K> {

		public abstract <T> T doExecute(K realKey);

		public K wrapKey(K key) {
			if (key instanceof String) {
				return (K) (wrapPrefix((String)key));
			}
			return key;
		}
	}
	@Override
	public String wrapPrefix(String key) {
		if(null == key || key.trim().length() == 0){
			return null;
		}
		return new StringBuilder().append(prefix).append(
					":").append(key).toString();
	}

	/**
	 * TODO
	 * 
	 */
	public <K, V> void monitorRedis(
		InvokerMethodInfo methodInfo, K key, K realKey, V t, long timeElapseMs) {
		logger.debug(
			"redis cache: methodInfo={},key={},timeElapseMs={}", methodInfo, realKey,
			timeElapseMs);
		if (key instanceof String) {

		}
		else {

		}
	}

}
